# -*- coding: utf-8 -*-
# @Date    : 2024/2/17
# @Author  : zouy
# @Email: 874112501@qq.com
"""https://castatic.fengkongcloud.cn/pr/v1.0.4/slide/embed.html"""
import json
import os
import re
from datetime import datetime
import random
import base64
import time

import ddddocr
import requests
from Crypto.Cipher import DES
from loguru import logger

DIR_PATH = os.path.dirname(os.path.abspath(__file__))


class ShuMeiSlide:
    """数美验证码"""

    def __init__(self, organization):
        self.domain = 'https://captcha1.fengkongcloud.cn'
        self.static_domain = "https://castatic.fengkongcloud.cn"
        self.organization = organization
        self.ss = self.set_session()
        self.captcha_uuid = self.get_captcha_uuid()
        self.request_param = {
            'sdkver': "1.1.3",
            'rversion': "1.0.4",
        }

    def set_session(self):
        session = requests.Session()
        session.headers = {
            "Accept": "*/*",
            "Accept-Encoding": "gzip, deflate, br",
            "Accept-Language": "zh-CN,zh;q=0.9",
            "Host": "captcha1.fengkongcloud.cn",
            "Origin": self.static_domain,
            "Referer": self.static_domain,
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
        }
        return session

    @staticmethod
    def get_jsonp(text):
        jsonp_str = re.search(r"\((.*)\)", text, re.S).group(1)
        return json.loads(jsonp_str)

    @staticmethod
    def get_callback():
        """生成请求参数callback"""
        return 'sm_' + str(int(time.time() * 1000))

    @staticmethod
    def get_captcha_uuid():
        """生成请求参数captchaUuid smcp.min.js"""
        text = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678"
        random18_str = ''.join(random.choices(text, k=18))
        time_str = datetime.now().strftime('%Y%m%d%H%M%S')
        return time_str + random18_str

    @staticmethod
    def des_encry(string, key):
        """des加密"""
        plaintext = string.encode("utf-8")

        des = DES.new(key=key.encode("utf-8"), mode=DES.MODE_ECB)
        if len(plaintext) % 8:
            padding_length = 8 - len(plaintext) % 8
            # 手动添加ZeroPadding
            plaintext += b'\x00' * padding_length

        decry_text = des.encrypt(plaintext)
        b64_text = base64.b64encode(decry_text).decode()
        return b64_text

    @staticmethod
    def download_img(file_name, *, url):
        """下载验证码图片"""
        resp = requests.get(url, headers={
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"})
        pic_dir = os.path.join(DIR_PATH, 'pic')
        if not os.path.exists(pic_dir):
            os.mkdir(pic_dir)
        path = os.path.join(pic_dir, file_name)
        with open(os.path.join(DIR_PATH, path), mode="wb") as f:
            f.write(resp.content)
        return path

    @staticmethod
    def get_slide_distance(fg_name, bg_name):
        """获取滑块距离"""
        det = ddddocr.DdddOcr(det=False, ocr=False, show_ad=False)
        with open(os.path.join(DIR_PATH, 'pic', fg_name), 'rb') as f:
            target_bytes = f.read()
        with open(os.path.join(DIR_PATH, 'pic', bg_name), 'rb') as f:
            background_bytes = f.read()

        res = det.slide_match(target_bytes, background_bytes)

        print(res)
        return res['target'][0]

    @staticmethod
    def get_track(distance):
        """生成轨迹"""
        base_track = [
            [0, 0, 0], [0, -2, 102], [9, -3, 205],
            [44, -5, 303], [82, -4, 409], [125, -3, 503],
            [138, -2, 601], [166, -1, 702], [194, -1, 805],
            [211, -1, 903], [222, -1, 1005], [224, -1, 1101],
            [228, -1, 1201], [228, -1, 1316], [232, -2, 1401],
            [232, -2, 1514], [232, -2, 1601], [233, -2, 1705],
            [233, -2, 1801], [233, -2, 1902], [236, -3, 2001],
            [236, -3, 2101], [236, -3, 2201], [236, -3, 2309],
            [236, -3, 2402], [236, -3, 2512], [236, -3, 2601],
            [236, -3, 2715], [236, -3, 2809], [236, -3, 2902]
        ]
        random_y = random.randint(0, 5)
        radio = distance / base_track[-1][0]
        new_track = []
        for x, y, t in base_track:
            y = y + random_y if y else 0
            point = [round(x * radio), y, round(t * radio)]
            new_track.append(point)
        return new_track

    def request_conf(self):
        """1.获取配置信息(可省略)
        {'code': 1100,
         'message': 'success',
         'requestId': 'e81904d4f8f253bf5eea8711c36ef819',
         'riskLevel': 'PASS',
         'score': 0,
         'detail': {
             'css': '/pr/auto-build/v1.0.4-180/style.min.css',
             'domains': ['castatic.fengkongcloud.cn', 'castatic.fengkongcloud.com', 'castatic-a.fengkongcloud.com', 'castatic2.fengkongcloud.com'],
             'js': '/pr/auto-build/v1.0.4-180/captcha-sdk.min.js'
         }}
        """
        url = self.domain + "/ca/v1/conf"
        params = {
            "organization": self.organization,
            "callback": self.get_callback(),
            "lang": "zh-cn",
            "appId": "default",
            "channel": "DEFAULT",
            "captchaUuid": self.captcha_uuid,
            "sdkver": self.request_param['sdkver'],
            "rversion": self.request_param['rversion'],
            "model": "slide"
        }
        response = self.ss.get(url, params=params)
        resp_json = self.get_jsonp(response.text)
        logger.info(f'获取配置信息：{resp_json.get("detail")}')

    def request_register(self):
        """2.获取验证码图片及相关参数
        {'code': 1100,
         'message': 'success',
         'requestId': 'b7575ba7a344408cabe6e79ab7f7f6b0',
         'riskLevel': 'PASS',
         'score': 0,
         'detail': {'bg': '/crb/set-20220315/v2/40f44a59d5caf6a50bb9ff3da3f00ef4_bg.jpg',
            'bg_height': 300,
            'bg_width': 600,
            'domains': ['castatic.fengkongcloud.cn', 'castatic.fengkongcloud.com', 'castatic-a.fengkongcloud.com', 'castatic2.fengkongcloud.com'],
            'fg': '/crb/set-20220315/v2/40f44a59d5caf6a50bb9ff3da3f00ef4_fg.png',
            'k': 'Dojjl0pRLwc=',
            'l': 8,
            'rid': '20240217072532d568dde558a102a7a5'}
        }
        """
        url = self.domain + "/ca/v1/register"
        params = {
            "organization": self.organization,
            "data": "{}",
            "appId": "default",
            "model": "slide",
            "rversion": self.request_param['rversion'],
            "lang": "zh-cn",
            "channel": "DEFAULT",
            "callback": self.get_callback(),
            "sdkver": self.request_param['sdkver'],
            "captchaUuid": self.captcha_uuid
        }
        response = self.ss.get(url, params=params)
        resp_json = self.get_jsonp(response.text)
        detail = resp_json['detail']
        logger.info(f'获取验证码信息：{resp_json}')
        return detail

    def request_fverify(self, rid, distance, track):
        """3.滑块验证
         成功：{'code': 1100, 'message': 'success', 'requestId': 'fca303662cea02daa65b507b51e91b42', 'riskLevel': 'PASS'}
        """
        je = self.des_encry(str(distance / 300), "5ea96022")
        move_time = track[-1][-1] + random.randint(10, 100)
        ww = self.des_encry(str(move_time), '17a94a08')
        mu = self.des_encry(json.dumps(track, separators=(',', ':')), "e7e1eb0d")
        logger.info(f'{move_time=}  {distance=}\n{track=}')
        params = {
            "je": je,
            "ww": ww,
            "mu": mu,

            "rid": rid,
            "captchaUuid": self.captcha_uuid,
            "organization": self.organization,
            "callback": self.get_callback(),

            "rversion": self.request_param['rversion'],
            "sdkver": self.request_param['sdkver'],
            "nu": "C0kH/bWLjw8=",  # mouseEndX=300, "390aac0d"
            "dy": "Rfpr5oqb5y4=",  # trueHeight=150, "a9001672"
            "en": "y+ugz9NIWys=",
            "tb": "3jSn4gNaAVM=",  # 1,'6f5e9847'
            "kq": "mtlOTdT5LOE=",
            # 和register请求参数有关
            "mp": "WYfkIZp7GoA=",
            "oc": "h9oFKi8cHpg=",
            "xy": "YabT6nmJOC0=",  # "zh-cn"
            "jo": "l3aEINYnwpY=",

            "protocol": "180",
            "ostype": "web",
            "act.os": "web_pc",
        }
        url = self.domain + "/ca/v2/fverify"
        response = self.ss.get(url, params=params)
        data = self.get_jsonp(response.text)
        return data

    def run(self):
        """启动"""
        # 获取配置信息(可省略)
        # self.request_conf()

        # 获取验证码图片及相关参数
        detail_param = self.request_register()

        # 下载图片
        self.download_img('bg.png', url=self.static_domain + detail_param['bg'])
        self.download_img('fg.png', url=self.static_domain + detail_param['fg'])

        # 识别滑块距离
        distance = round(self.get_slide_distance('fg.png', bg_name='bg.png') / 2)
        logger.info(f'{distance=}')

        # 生成轨迹
        track = self.get_track(distance)

        # 滑块验证
        result = self.request_fverify(detail_param['rid'], distance, track)
        if result.get('riskLevel') == 'PASS':
            logger.success(f'成功：{result}')
        else:
            logger.warning(f'失败：{result}')


if __name__ == '__main__':
    # /pr/v1.0.4/slide/embed.html中内容： organization: isDev ? 'fe1iHzsMKi3vHLmqtHyl' : 'RlokQwRlVjUrTUlkIqOg'
    organization = "RlokQwRlVjUrTUlkIqOg"
    sm = ShuMeiSlide(organization)
    sm.run()
